package com.gitee.wsl.doc.prosemirror.model.content.ext

import com.gitee.wsl.doc.prosemirror.model.attr.Attrs
import com.gitee.wsl.doc.prosemirror.model.content.ContentMatch
import com.gitee.wsl.doc.prosemirror.model.content.NodeRange
import com.gitee.wsl.doc.prosemirror.model.node.NodeBase
import com.gitee.wsl.doc.prosemirror.model.node.NodeType
import kotlin.collections.plus


// Try to find a valid way to wrap the content in the given range in a node of the given type. May
// introduce extra nodes around and inside the wrapper node, if necessary. Returns null if no valid
// wrapping could be found. When `innerRange` is given, that range's content is used as the content
// to fit into the wrapping, instead of the content of `range`.
fun NodeRange.findWrapping(
    nodeType: NodeType,
    attrs: Attrs? = null,
    innerRange: NodeRange = this
): List<NodeBase>? {
    val around = findWrappingOutside(nodeType) ?: return null
    val inner = innerRange.findWrappingInside(nodeType) ?: return null
    return around.map(::withAttrs) + NodeBase(type = nodeType, attrs) + inner.map(::withAttrs)
}

fun withAttrs(type: NodeType) = NodeBase(type, null)


fun NodeRange.findWrappingOutside(type: NodeType): List<NodeType>? {
    val range = this
    val parent = range.parent
    val startIndex = range.startIndex
    val endIndex = range.endIndex
    val around = parent.contentMatchAt(startIndex).findWrapping(type) ?: return null
    val outer = if (around.isNotEmpty()) around[0] else type
    return if (parent.canReplaceWith(startIndex, endIndex, outer)) around else null
}

fun NodeRange.findWrappingInside( type: NodeType): List<NodeType>? {
    val range = this
    val parent = range.parent
    val startIndex = range.startIndex
    val endIndex = range.endIndex
    val inner = parent.child(startIndex)
    val inside = type.contentMatch.findWrapping(inner.type) ?: return null
    val lastType = if (inside.isNotEmpty()) inside.last() else type
    var innerMatch: ContentMatch? = lastType.contentMatch
    var i = startIndex
    while (innerMatch != null && i < endIndex) {
        innerMatch = innerMatch.matchType(parent.child(i).type)
        i++
    }
    if (innerMatch == null || !innerMatch.validEnd) return null
    return inside
}